package com.matrix.utilities;

import java.util.regex.Pattern;
/**
 * Parser to convert a given String to matrix or a vector.
 * 
 * Variables in this class are : String token, int numOfCol, int numOfRow.
 * 
 * Expected Matrix input: [1, 2, 3, 4; 2, 3, 4, 5; 1, 2, 3, 4]
 * 						 OR
 * 						  [1 2 3 4 ; 2 3 4 5; 1 2 3 4]
 * 						 OR
 * 						  1 2 3 4;1 2 3 4; 1 2 3 4
 * 						 OR 
 * 					   	  [1#2#3#4;1#2#3%4;1#2#3#4]
 * 
 * Expected Vector input: [1, 2, 3, 4];
 * 
 * Checks:	1- Matrix needs to be in the right format
 *			2- Handles all issues of matrix input
 *
 * Features-(Priority) : 
 * 	1- Flexible Reader-(high): Users can choose whatever delimiter they want
 *	2- Ability to choose row separator.
 *	3- Parse and output in Matrix or Vector Format.
 *
 * @author Sultan-PC
 * @version 1.00
 * @date Feb 10th, 2011
 * @see http://www.code.google.com/p/matrixcalculator
**/
public class ParserNew
{
	/**
	 * Instance Variables
	 */
	final static int HAS_ONE_ROW = 99;
	private String token;
	private int numCol = 0, numRow = 0;
	private String separator = ";"; // users can choose this char in the settings
	private String[] array;
	
	/**
	 * Default Constructor
	 */
	public ParserNew()
	{
		this.setToken("");
	}
	
	/**
	 * Overridden Constructor
	 */
	public ParserNew(String what)
	{
		this.setToken(what);
	}
	
	/**
	 * This function parse the string stored in the parser object and 
	 * returns either a matrix or a vector based on the argument.
	 * if the argument is a string with the value "matrix", it returns a 2d array of the matrix
	 * if the argument is a string with the value "vector", it returns an array of vector.
	 * 
	 * @param String with value "matrix" or "vector"
	 * @return 2d array if argument is "matrix", and one dimentional array if argument is "vector"
	 * 
	 */
	public double[][] parse()
	{
		if(!(this.checkFormat()&&this.checkDelimiter()))
		{
			return null;
		}
		else
		{
			int count = 0;
			double [][] va = new double[this.getNumRow()][this.getNumCol()];
			
			for(int i = 0; i < this.getNumRow(); i++)
			{
				for(int j = 0; j < this.getNumCol(); ++j)
				{
					String a = this.array[count++];
					va[i][j] = Double.parseDouble(a);
				}
			}
			//this.clear();
			return va;
		}
	}

	/**
	 * This function checks the format of the string stored in the parser object.
	 * The pseduocode for this function is as follows:
	 * 1- validate that one of the following conditions is true:
	 * 			- Condition one: The string starts with '[' and ends with ']'
	 * 			- Condtion two: The string start with a number and ends with a number.
	 * 		if both condtions are false, return -1 which means the format is wrong.
	 * 2- remove all commas, and nonDigit characters from the string so that all remaining 
	 * 	  characters are the order of the matrix.
	 * 	  NOTE that the "fixArray" function is used here because the split 
	 * 		   function in the Pattern class returns an array whose first cell
	 * 		   is empty, so we use the function fixArray to delete the empty cell.
	 * 3- assign tche length of the array to the variable NbyM which is the total number
	 * 	  of elements in the matrix.
	 * 4- assign the number of columns by calling the function "getColDimension".
	 * 5- perform a check to see if the remainder of dividing NbyM by the number of columns
	 * 	  equals to zero. If remainder is zero that means there is no missing elements in the matrix
	 * 	  and the input is correct, else return -1 for input error.
	 * 6- set the number of rows to to the result of dividing NbyM by numOfColumns.
	 * 	  this variable will be used in other functions.
	 * 
	 * @return positive integer if format is good, else return -1
	 */
	public boolean checkFormat()
	{
		int NbyM = 0; // number of columns times number of rows
		String firstChar = this.getToken().substring(0,1);
		String lastChar = this.getToken().substring(this.getToken().length()-1);
		boolean condition = this.getToken().startsWith("[") && this.getToken().endsWith("]");
		boolean condition2 = this.isDigit(firstChar) && this.isDigit(lastChar);
		if(!(condition || condition2))
		{
			return false;
		}
		Pattern p = Pattern.compile("[,[^0-9^.^e^E^-^+]]+"); // all non digit
		this.array = this.fixArray(p.split(this.getToken()));
		NbyM = this.array.length;
		int numOfCols = this.getColDimension(this.getToken());
		if(numOfCols == HAS_ONE_ROW)
		{
			this.setNumCol(this.array.length);
			this.setNumRow(1);
			return true;
		}
		this.setNumCol(this.getColDimension(this.getToken()));
		//print(this.array);
		if(NbyM % this.getNumCol() != 0) return false;
		this.setNumRow(NbyM/this.getNumCol());
		return true;
	}
	
	/**
	 * if the user chooses to separate the entries by non-digit character,
	 * then this method checks to see if those delimiters are inputed correctly
	 * @return
	 */
	public boolean checkDelimiter()
	{		
		Pattern p = Pattern.compile("[[-+]?\\d*\\.?\\d+([eE][-+]?\\d+)? \\s \\[\\]]+");
		String[] array = this.fixArray(p.split(this.getToken()));
		int numOfDelimiter = ((this.getNumCol()-1)*this.getNumRow())+
								this.decrement(this.getNumRow());
		if(array.length == this.decrement(this.getNumRow())
				|| (array.length) == numOfDelimiter)
		{
			return true;
		}
		return false;
	}
	
	/**
	 * 
	 * @param what
	 * @return
	 */
	public int getColDimension(String what)
	{
		String temp = "";
		int indexOfSeparator = -1;
		for(int i = 0; i < what.length(); ++i)
		{
			temp = what.substring(i,i+1);
			if(temp.equalsIgnoreCase(this.separator))
			{
				indexOfSeparator = i;
				//System.out.println("Index is "+i);
				break;
			}
		}// end for
		if(indexOfSeparator != -1) // means it s a row vector
		{
			temp = what.substring(0, indexOfSeparator+1);
			Pattern p = Pattern.compile("[,[^0-9^.^E^e^-]]+");	   
			String[] array = this.fixArray(p.split(temp));
			//this.print(array);
			return array.length;
		}
		return HAS_ONE_ROW;
	}
	
	
	/**
	 * Accessors and Mutators
	 */
	
	public String getToken() 
	{
		return token;
	}

	public void setToken(String token) 
	{
		this.token = token;
	}
	
	/**
	 * Helper Methods
	 */
	
	
	/***
	 * 
	 * @param string
	 * @return true if parameter is digit, else returns false.
	 * @type boolean
	 * 
	 */
	private boolean isDigit(String what)
	{
		try
		{
			Double.parseDouble(what);
			return true;
		}
		catch(Exception e)
		{
			return false;
		}
	}// end private boolean isDigit(String what)

	/**
	 * 
	 * @return
	 */
	protected int getNumCol() {
		return numCol;
	}
	/**
	 * 
	 * @param numCol
	 */
	protected void setNumCol(int numCol) {
		this.numCol = numCol;
	}
	/**
	 * 
	 * @return
	 */
	protected int getNumRow() {
		return numRow;
	}
	/**
	 * 
	 * @param numRow
	 */
	protected void setNumRow(int numRow) {
		this.numRow = numRow;
	}
	
	/**
	 * 
	 * @param what
	 * @return
	 */
	public int decrement(int what)
	{
		return what-1;
	}
	
	/**
	 * 
	 * @param what
	 */
	public void print(String[] what)
	{
		for(int i = 0; i < what.length;i++)
		{
			System.out.println("VALUE["+i+"] is ["+what[i]+"]");
		}
	}
	
	public void printMatrix(double[][] that)
	{		
		if(that != null)
		{
			for(int i = 0; i < that.length;i++)
			{
				for(int j = 0; j < that[0].length; j++)
				{
					System.out.println("VALUE["+i+"]["+j+"] is "+that[i][j]);
				}
			}
		}
		else
			System.out.println("what is null");
	}
	
	
	
	/**
	 * This method checks if the first cell in the passed array 
	 * is empty and if it is then it returns a new copy of the array
	 * without the empty cell.
	 * @param array of type String
	 * @return a copy array if the condition above is met, 
	 * 			otherwise it returns the same string.
	 */
	public String[] fixArray(String[] what)
	{
		if(what.length == 0)
		{
			return what;
		}
		String[] temp = new String[what.length-1];
		if(what[0].equalsIgnoreCase(""))
		{
			for(int i = 0; i < what.length-1; i++)
			{
				temp[i] = what[i+1];
			}
			return temp;
		}
		return what;
	}
	
	public double[][] getMatrix(String s) throws Exception
	{
		Pattern p = Pattern.compile("[\\;]+");
		String[] ar = p.split(s);
		p = Pattern.compile("[\\s]+");
		String[] ar2 = p.split(ar[0]);
		int numCols = ar2.length;
		double[][] matrix = new double[ar.length][numCols];
		if(ar2.length>10 || ar.length>10)
		{
			throw new Exception("Matrix dimensions cannot exceed 10");
		}
		int k =0;
		for(int i=0; i<ar.length; i++)
		{
			ar2 = p.split(ar[i]);
			
			if(ar2.length != numCols)
			{
				ar2 = fixArray(ar2);
				if(ar2.length != numCols)
				{
					throw new Exception("Ragged Matrix each row needs to have the same dimension.");
				}
			}
			//System.out.println("gmatrix: \n"+ar[i]);
			for(int j=0; j<numCols; j++)
			{
				if(!isDigit(ar2[j]))
				{
					throw new Exception("Non numerical value");
				}
				matrix[i][j] = Double.parseDouble(ar2[j]);
			}
		}
		return matrix;
	}
	public double[] getVector(String s) throws Exception
	{
		double[][] d = getMatrix(s);
		if(d.length>1)
		{
			throw new Exception("Vectors are only one dimension");
		}
		return d[0];
	}
	
	public void clear()
	{
		this.setNumRow(0);
		this.setNumCol(0);
		this.setToken("");
		this.array = null;
	}
} //EOF